﻿#region Header

/*
Remote views based on linq
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using paravili.Services;
using Sebarf.Services.Interfaces;

#endregion

namespace paravili.Steps {
	/// <summary>
	/// tries to find movement between the given list of points
	/// </summary>
	public class FindMovementBetweenClustersWithLPL :
		ProcessStepConverterWithMeasurement<IList<IList<ColoredPoint>>, IList<Cluster>> {

		#region Public Properties

		[ConfigurableNumericValue(Name = "MaxDistanceBetweenTwoCluster", RangeFrom = 10, RangeTo = 500)]
		public int MaxDistanceBetweenTwoCluster { get; set; }

		[ConfigurableNumericValue(Name = "MaxFramesBetweenTwoCluster", RangeFrom = 1, RangeTo = 50)]
		public int MaxFramesBetweenTwoCluster { get; set; }

		[ConfigurableNumericValue(Name = "WeightOfDistance", RangeFrom = 1, RangeTo = 10)]
		public int WeightOfDistance { get; set; }

		[ConfigurableNumericValue(Name = "WeightOfTime", RangeFrom = 1, RangeTo = 10)]
		public int WeightOfTime { get; set; }

		[ConfigurableNumericValue(Name = "WeightOfColour", RangeFrom = 1, RangeTo = 10)]
		public int WeightOfColour { get; set; }

		[ConfigurableNumericValue(Name = "WeightConvexHullSize", RangeFrom = 1, RangeTo = 10)]
		public int WeightConvexHullSize { get; set; }

		[ConfigurableNumericValue(Name = "MaxMatchingPoints", RangeFrom = 1, RangeTo = 100000)]
		public int MinMatchingPoints { get; set; }

		[ServiceRequest]
		public TimeServiceIfc TimeService { get; set; }

		[ServiceRequest]
		public IResetHandlerService ResetHandlerService {
			set { value.Reset += OnReset; }
		}

		[ServiceRequest]
		public IDoMatchingService DoMatchingService {
			set;
			get;
		}

		#endregion

		#region Public Methods

		public FindMovementBetweenClustersWithLPL() {
			MaxDistanceBetweenTwoCluster = 30;
			MaxFramesBetweenTwoCluster = 4;
			WeightOfDistance = 2;
			WeightOfTime = 1;
			WeightOfColour = 1;
			WeightConvexHullSize = 1;
			MinMatchingPoints = 100000;
			DoReset();
		}

		#endregion

		#region Private Methods

		protected override IList<Cluster> Convert(IList<IList<ColoredPoint>> toProcess) {
			if (toProcess != null) {
				// prepare data
				foreach (var pointList in toProcess) {
					m_availableClusters.Add(CreateClusterStructureOutOfPointsOfACluster(pointList));
				}
			}
			IList<Cluster> toReturn = FindMovement();
			return toReturn;
		}

		/// <summary>
		/// create a cluster out of list of <see cref="ColoredPoint"/>
		/// </summary>
		/// <param name="points"></param>
		/// <returns></returns>
		private Cluster CreateClusterStructureOutOfPointsOfACluster(IList<ColoredPoint> points) {
			double xTotal = 0;
			double yTotal = 0;
			double maxX = 0;
			double minX = 0;
			double maxY = 0;
			double minY = 0;
			double colorX = 0;
			double colorY = 0;
			double colorZ = 0;
			int pointsCount = points.Count;
			foreach (ColoredPoint p in points) {
				int x = p.P.X;
				int y = p.P.Y;
				colorX += p.Color.X;
				colorY += p.Color.Y;
				colorZ += p.Color.Z;
				xTotal += x;
				yTotal += y;
				if (maxX < x) {
					maxX = x;
				}
				else if (minX > x) {
					minX = x;
				}
				if (maxY < y) {
					maxY = y;
				}
				else if (minY > y) {
					minY = y;
				}
			}
			return new Cluster {
				Points = points,
				AverageXValue = xTotal / pointsCount,
				AverageYValue = yTotal / pointsCount,
				Time = TimeService.GetTime(),
				HulSize = Math.Abs(maxX - minX) * Math.Abs(maxY - minY),
				ColorX = colorX / pointsCount,
				ColorY = colorY / pointsCount,
				ColorZ = colorZ / pointsCount
			};
		}

		/// <summary>
		/// Reset handler
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnReset(Object sender, EventArgs args) {
			DoReset();
		}

		/// <summary>
		/// resets the step
		/// </summary>
		private void DoReset() {
			m_availableClusters.Clear();
			m_minDistance = double.MaxValue;
			m_minColor = double.MaxValue;
			m_minSize = double.MaxValue;
			m_minTime = double.MaxValue;
			m_maxColor = double.MinValue;
			m_maxDistance = double.MinValue;
			m_maxSize = double.MinValue;
			m_maxTime = double.MinValue;
		}

		/// <summary>
		/// Calculates how close two given clusters are
		/// </summary>
		/// <param name="first"></param>
		/// <param name="last"></param>
		/// <returns></returns>
		private double MatchFunction(Cluster first, Cluster last) {
			if (first == last) {
				return double.MinValue;
			}
			if (last.PreviousCluster != null) {
				return double.MinValue;
			}
			double matchingPoints = 0;
			double distance =
				Math.Pow(
					Math.Pow(first.AverageXValue - last.AverageXValue, 2) + Math.Pow(first.AverageYValue - last.AverageYValue, 2),
					0.5);
			if (MaxDistanceBetweenTwoCluster < distance) {
				return double.MinValue;
			}
			m_minDistance = Math.Min(m_minDistance, distance);
			m_maxDistance = Math.Max(m_maxDistance, distance);

			matchingPoints -= (distance / ReturnOneIfNull(m_maxDistance - m_minDistance) * WeightOfDistance);

			int timeDiff = Math.Abs(first.Time - last.Time);
			if (first.Time >= last.Time || timeDiff > MaxFramesBetweenTwoCluster) {
				return double.MinValue;
			}
			m_minTime = Math.Min(m_minTime, timeDiff);
			m_maxTime = Math.Max(m_maxTime, timeDiff);
			matchingPoints -= (timeDiff - 1) / ReturnOneIfNull(m_maxTime - m_minTime) * WeightOfTime;

			double colorDifferenceDifference =
				Math.Pow(
					Math.Pow(first.ColorY - last.ColorY, 2) + Math.Pow(first.ColorX - last.ColorX, 2) +
					Math.Pow(first.ColorZ - last.ColorZ, 2), 0.5);

			m_minColor = Math.Min(m_minColor, colorDifferenceDifference);
			m_maxColor = Math.Max(m_maxColor, colorDifferenceDifference);
			matchingPoints -= (colorDifferenceDifference) / ReturnOneIfNull(m_maxColor - m_minColor) * WeightOfColour;

			double diffConvexHullSize = Math.Abs(first.HulSize - last.HulSize);

			m_minSize = Math.Min(m_minSize, diffConvexHullSize);
			m_maxSize = Math.Max(m_maxSize, diffConvexHullSize);
			matchingPoints -= ((diffConvexHullSize) / ReturnOneIfNull(m_maxSize - m_minSize)) * WeightConvexHullSize;

			if (matchingPoints < (-1) * MinMatchingPoints) {
				matchingPoints = Double.MinValue;
			}
			return matchingPoints;
		}

		/// <summary>
		/// helper function to avoid null values
		/// </summary>
		/// <param name="d"></param>
		/// <returns></returns>
		private static double ReturnOneIfNull(double d) {
			if (d == 0) {
				return 1;
			}
			return d;
		}

		/// <summary>
		/// match clusters together
		/// </summary>
		/// <returns></returns>
		private IList<Cluster> FindMovement() {
			if (m_availableClusters.Count < 1) {
				return null;
			}
			// remove to old clusters
			int maxTime = MaxFramesBetweenTwoCluster;
			int time = TimeService.GetTime();
			m_availableClusters.RemoveAll(c => time - c.Time > maxTime);

			// rember found clusters
			IList<Cluster> toReturn = new List<Cluster>();
			IList<Cluster> toRemove = new List<Cluster>();
			Cluster firstCluster;
			Cluster lastCluster;
			int[,] distanceBetweenClusters = new int[m_availableClusters.Count, m_availableClusters.Count];
			var clusters = m_availableClusters.ToArray();

			//StringBuilder dataToWrite = new StringBuilder();
			//dataToWrite.Append("__matrixSize");
			//dataToWrite.AppendLine();
			//dataToWrite.Append(clusters.Length);
			//dataToWrite.AppendLine();
			//dataToWrite.Append("__matrixSize");
			//dataToWrite.AppendLine();
			//dataToWrite.Append("__data");
			//dataToWrite.AppendLine();
			//// prepare data
			for (int i = 0; i < clusters.Length; i++) {
				for (int j = 0; j < clusters.Length; j++) {
					distanceBetweenClusters[i, j] = (int)(MatchFunction(clusters[i], clusters[j]) * 1000);

				}
			}
			//dataToWrite.Append("__data");
			//string path = System.IO.Directory.GetCurrentDirectory() + "\\data.txt";
			//// write data
			//System.IO.File.WriteAllText(path, dataToWrite.ToString());
			//StatisticValuesProviderService.SetValueToShow("Movements.Found", toReturn.Count);

			//// execute solver
			//Process p = new Process();
			//p.StartInfo = new ProcessStartInfo("lplc.exe", "Movement.lpl");
			//p.StartInfo.WorkingDirectory = System.IO.Directory.GetCurrentDirectory();
			//p.Start();
			//p.WaitForExit();

			//// read result
			//string solution = System.IO.Directory.GetCurrentDirectory() + "\\Movement.nom";
			//var content = System.IO.File.ReadAllLines(solution);
			var matches = DoMatchingService.FindMatch(distanceBetweenClusters);
			foreach (var match in matches) {
				int firstClusterIndex = match.FromIndex - 1;
				int lastClusterIndex = match.ToIndex - 1;
				lastCluster = clusters[lastClusterIndex];
				firstCluster = clusters[firstClusterIndex];
				lastCluster.PreviousCluster = firstCluster;
				toReturn.Add(lastCluster);
				toRemove.Add(firstCluster);
			}
			foreach (Cluster cluster in toRemove) {
				m_availableClusters.Remove(cluster);
			}
			StatisticValuesProviderService.SetValueToShow("Movements.Found", toReturn.Count);
			return toReturn;
		}

		#endregion

		#region Private Fields

		private readonly List<Cluster> m_availableClusters = new List<Cluster>();
		private double m_maxColor;
		private double m_maxDistance;
		private double m_maxSize;
		private double m_maxTime;
		private double m_minColor;
		private double m_minDistance;
		private double m_minSize;
		private double m_minTime;

		#endregion
	}
}